{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "3b4e12fa-8a0e-4aea-8560-1f3fddbc5db3",
   "metadata": {},
   "source": [
    "# CW-Husky Glitch Testing\n",
    "\n",
    "Runs some of the same glitch tests found in test_husky.py, but (much) longer. Besides the additional validation, this notebook can be helpful to diagnose any issues that may pop up."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "399307f9-d5d2-41fa-ad05-81d1f6eee4f9",
   "metadata": {},
   "outputs": [],
   "source": [
    "SCOPETYPE = 'OPENADC'\n",
    "PLATFORM = 'CW308_STM32F3'\n",
    "FORCE_LATEST_BITFILE = False"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5138d053-104b-4b21-b3ce-81ce8faa6fd7",
   "metadata": {},
   "outputs": [],
   "source": [
    "if FORCE_LATEST_BITFILE:\n",
    "    import chipwhisperer as cw\n",
    "\n",
    "    try:\n",
    "        if not scope.connectStatus:\n",
    "            scope.con()\n",
    "    except NameError:\n",
    "        scope = cw.scope(name='Husky', bitstream=\"/home/jpnewae/git/cw_husky/fpga/vivado/cwhusky.runs/impl_no_ilas/cwhusky_top.bit\")\n",
    "        #scope = cw.scope(bitstream=\"/home/jpnewae/git/cw_husky/fpga/vivado/cwhusky.runs/impl_1/cwhusky_top.bit\")\n",
    "\n",
    "    try:\n",
    "        if SS_VER == \"SS_VER_2_1\":\n",
    "            target_type = cw.targets.SimpleSerial2\n",
    "        elif SS_VER == \"SS_VER_2_0\":\n",
    "            raise OSError(\"SS_VER_2_0 is deprecated. Use SS_VER_2_1\")\n",
    "        else:\n",
    "            target_type = cw.targets.SimpleSerial\n",
    "    except:\n",
    "        SS_VER=\"SS_VER_1_1\"\n",
    "        target_type = cw.targets.SimpleSerial\n",
    "\n",
    "    try:\n",
    "        target = cw.target(scope, target_type)\n",
    "    except:\n",
    "        print(\"INFO: Caught exception on reconnecting to target - attempting to reconnect to scope first.\")\n",
    "        print(\"INFO: This is a work-around when USB has died without Python knowing. Ignore errors above this line.\")\n",
    "        scope = cw.scope(bitstream=\"/home/jpnewae/git/cw_husky/fpga/vivado/cwhusky.runs/impl_no_ilas/cwhusky_top.bit\")\n",
    "        target = cw.target(scope, target_type)\n",
    "\n",
    "    print(\"INFO: Found ChipWhisperer😍\")\n",
    "\n",
    "    import time\n",
    "    time.sleep(0.05)\n",
    "    scope.default_setup()\n",
    "    def reset_target(scope):\n",
    "        if PLATFORM == \"CW303\" or PLATFORM == \"CWLITEXMEGA\":\n",
    "            scope.io.pdic = 'low'\n",
    "            time.sleep(0.1)\n",
    "            scope.io.pdic = 'high_z' #XMEGA doesn't like pdic driven high\n",
    "            time.sleep(0.1) #xmega needs more startup time\n",
    "        elif \"neorv32\" in PLATFORM.lower():\n",
    "            raise IOError(\"Default iCE40 neorv32 build does not have external reset - reprogram device to reset\")\n",
    "        else:  \n",
    "            scope.io.nrst = 'low'\n",
    "            time.sleep(0.05)\n",
    "            scope.io.nrst = 'high_z'\n",
    "            time.sleep(0.05)\n",
    "    reset_target(scope)\n",
    "\n",
    "else:\n",
    "    %run \"../../Setup_Scripts/Setup_Generic.ipynb\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a105aa9f-b0d1-4e66-b664-3fdeeda7defe",
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.fpga_buildtime"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1e356302-8883-44f9-8433-ad81cedc8eac",
   "metadata": {},
   "outputs": [],
   "source": [
    "from tqdm.notebook import tnrange\n",
    "import random\n",
    "import numpy as np\n",
    "\n",
    "scope.adc.clip_errors_disabled = True\n",
    "scope.adc.lo_gain_errors_disabled = True\n",
    "\n",
    "def reset_setup():\n",
    "    scope.trigger.module = 'basic'\n",
    "    scope.trigger.triggers = 'tio4'\n",
    "    scope.io.tio1 = \"serial_rx\"\n",
    "    scope.io.tio2 = \"serial_tx\"\n",
    "    scope.io.hs2 = \"clkgen\"\n",
    "    scope.adc.timeout = 3\n",
    "    scope.adc.offset = 0\n",
    "    scope.glitch.enabled = False\n",
    "    scope.LA.enabled = False\n",
    "    scope.LA.clkgen_enabled = True\n",
    "    scope.LA.capture_depth = 512\n",
    "    scope.LA.downsample = 1\n",
    "    scope.trace.enabled = False\n",
    "    target.baud = 38400 * 10 / 7.37\n",
    "\n",
    "def setup_glitch(offset, width, oversamp, LA=False):\n",
    "    # set up glitch:\n",
    "    scope.glitch.enabled = True\n",
    "    scope.glitch.clk_src = 'pll'\n",
    "    scope.clock.pll.update_fpga_vco(600e6)\n",
    "    scope.glitch.repeat = 1\n",
    "    scope.glitch.output = 'glitch_only'\n",
    "    scope.glitch.trigger_src = 'ext_single'\n",
    "    scope.glitch.offset = offset\n",
    "    scope.glitch.width = width\n",
    "    assert scope.glitch.mmcm_locked\n",
    "    if LA:\n",
    "        scope.LA.enabled = True\n",
    "        scope.LA.oversampling_factor = oversamp\n",
    "        scope.LA.capture_group = 'glitch'\n",
    "        scope.LA.trigger_source = \"glitch_source\"\n",
    "        #scope.LA.trigger_source = \"glitch_trigger\"\n",
    "        scope.LA.capture_depth = 512\n",
    "        assert scope.LA.locked\n",
    "    else:\n",
    "        scope.LA.enabled = True\n",
    "\n",
    "def reset_target():\n",
    "    scope.io.nrst = 0\n",
    "    time.sleep(0.2)\n",
    "    scope.io.nrst = 'high_z'\n",
    "    time.sleep(0.2)\n",
    "\n",
    "def find0to1trans(data):\n",
    "    pattern = [0,1]\n",
    "    return [i for i in range(0,len(data)) if list(data[i:i+len(pattern)])==pattern]\n",
    "\n",
    "def test_missing_glitch_sweep_offset(clock, vco, span, width, num_glitches, reps, stepsize, LA=False):\n",
    "    # Checks for missing glitches (https://github.com/newaetech/chipwhisperer-husky-fpga/issues/4)\n",
    "    setup_clock(clock)\n",
    "    scope.clock.pll.update_fpga_vco(vco)\n",
    "    scope.adc.samples = 16\n",
    "    errors = []\n",
    "    for offset in tnrange(scope.glitch.phase_shift_steps//2-span, scope.glitch.phase_shift_steps//2+span, stepsize):\n",
    "        e = test_missing_glitch_single_offset(offset, width, num_glitches, reps, LA)\n",
    "        if e:\n",
    "            errors.append(e)\n",
    "    assert errors == []\n",
    "\n",
    "def setup_clock(clock):\n",
    "    reset_setup()\n",
    "    scope.clock.clkgen_freq = clock\n",
    "    scope.clock.adc_mul = 1\n",
    "    time.sleep(0.1)\n",
    "    assert scope.clock.pll.pll_locked == True\n",
    "    assert scope.clock.adc_freq == clock\n",
    "    target.baud = 38400 * clock / 1e6 / 7.37\n",
    "    reset_target()\n",
    "\n",
    "def test_missing_glitch_single_offset(offset, width, num_glitches, reps, LA):\n",
    "    setup_glitch(offset, width, 1, LA)\n",
    "    scope.glitch.num_glitches = num_glitches\n",
    "    scope.io.tio4 = 'high_z'\n",
    "    errors = []\n",
    "    for i in range(reps):\n",
    "        ext_offsets = []\n",
    "        for j in range(num_glitches):\n",
    "            ext_offsets.append(random.randrange(2,5))\n",
    "        scope.glitch.ext_offset = ext_offsets\n",
    "        scope.glitch.repeat = [1]*num_glitches\n",
    "        if LA:\n",
    "            scope.LA.arm()\n",
    "        trace = cw.capture_trace(scope, target, bytearray(16), bytearray(16))\n",
    "        assert trace is not None, 'capture failed (offset=%d, rep=%d)' % (offset, i)\n",
    "        if scope.glitch.state != 'idle':\n",
    "            errors.append(offset)\n",
    "            print(\"ERROR: not in idle! state = %s, offset = %d, rep = %d\" % (scope.glitch.state, offset, i))\n",
    "            scope.glitch.state = None\n",
    "        if LA:\n",
    "            assert not scope.LA.fifo_empty()\n",
    "            raw = scope.LA.read_capture_data()\n",
    "    return errors\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "823cf3c2-024c-46f9-a8d9-95dacb191225",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Test for missing glitches: run sweep around area of interest with lots of repetitions:\n",
    "# This should take about 15 minutes:\n",
    "test_missing_glitch_sweep_offset(clock=10e6, vco=600e6, span=100, width=1000, num_glitches=10, reps=5, stepsize=1, LA=True)\n",
    "#test_missing_glitch_sweep_offset(clock=10e6, vco=1200e6, span=100, width=1000, num_glitches=10, reps=10, stepsize=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4e011dde-cb7a-46c1-a5d0-9196cdd5984b",
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.glitch.state"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "085bd4a0-8f41-46c4-b5e8-8ba6e3b364cd",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Test for missing glitches: run sweep around area of interest with lots of repetitions:\n",
    "# This should take about 40 minutes:\n",
    "#test_missing_glitch_sweep_offset(clock=10e6, vco=600e6, span=3360, width=1000, num_glitches=10, reps=10, stepsize=1)\n",
    "test_missing_glitch_sweep_offset(clock=10e6, vco=600e6, span=3360, width=1000, num_glitches=10, reps=1, stepsize=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d8edc932-7051-4f8c-be47-ce56e3638e60",
   "metadata": {},
   "outputs": [],
   "source": [
    "# example to test a specific offset:\n",
    "setup_clock(100e6)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a3132080-7e4b-436b-8579-79c5100fda8c",
   "metadata": {},
   "outputs": [],
   "source": [
    "test_missing_glitch_single_offset(offset=1632, width=1000, num_glitches=10, reps=10, LA=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0bec9e24-e76b-4c66-8d5d-33a7b0700013",
   "metadata": {},
   "outputs": [],
   "source": [
    "test_missing_glitch_single_offset(offset=1532, width=1000, num_glitches=10, reps=10, LA=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4187ee86-cea7-40f6-a274-1d935a70102d",
   "metadata": {},
   "outputs": [],
   "source": [
    "# To investigate behaviour using external logic analyzer:\n",
    "scope.userio.mode = 'fpga_debug'\n",
    "scope.fpga_reg_write(109, [4])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ba3976a0-185a-4a78-b8f3-21a64398378b",
   "metadata": {},
   "source": [
    "Variant with no target attached (useful for testing at clocks > max STM32 clock):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b5242114-5e67-4d44-87fc-48324e1f5b56",
   "metadata": {},
   "outputs": [],
   "source": [
    "def test_missing_glitch_sweep_offset_notarget(clock, vco, span, width, oversamp, num_glitches, reps, stepsize):\n",
    "    # Checks for missing glitches (https://github.com/newaetech/chipwhisperer-husky-fpga/issues/4)\n",
    "    setup_clock(clock)\n",
    "    scope.clock.pll.update_fpga_vco(vco)\n",
    "    scope.adc.samples = 16\n",
    "    errors = []\n",
    "    for offset in tnrange(scope.glitch.phase_shift_steps//2-span, scope.glitch.phase_shift_steps//2+span, stepsize):\n",
    "        e = test_missing_glitch_single_offset_notarget(offset, width, oversamp, num_glitches, reps)\n",
    "        if e:\n",
    "            errors.append(e)\n",
    "    assert errors == []\n",
    "\n",
    "def test_missing_glitch_single_offset_notarget(offset, width, oversamp, num_glitches, reps):\n",
    "    #offset = 1632 # 10M\n",
    "    #num_glitches = 1\n",
    "    #reps = 100\n",
    "    #oversamp = 4\n",
    "    #LA = True\n",
    "\n",
    "    scope.io.tio4 = 0\n",
    "    assert scope.io.tio_states[3] == 0, 'This will not work if IO4 is not cleared.'\n",
    "    setup_glitch(offset, width, oversamp, True)\n",
    "    scope.glitch.trigger_src = 'manual'\n",
    "    scope.glitch.num_glitches = num_glitches\n",
    "    errors = []\n",
    "    for i in range(reps):\n",
    "        ext_offsets = []\n",
    "        for j in range(num_glitches):\n",
    "            ext_offsets.append(random.randrange(2,5))\n",
    "        scope.glitch.ext_offset = ext_offsets\n",
    "        scope.glitch.repeat = [1]*num_glitches\n",
    "        scope.LA.arm()\n",
    "        scope.glitch.manual_trigger()\n",
    "        assert not scope.LA.fifo_empty()\n",
    "        raw = scope.LA.read_capture_data()\n",
    "        glitchenable = scope.LA.extract(raw, 6)\n",
    "        glitchenlen = len(np.where(glitchenable == 1)[0])\n",
    "        if glitchenlen == 0:\n",
    "            errors.append(offset)\n",
    "            print('Offset %d, iteration %d: Expected glitch length = %d, got %d' % (offset, i, oversamp, glitchenlen))\n",
    "    scope.io.tio4 = 'high_z' # return to default\n",
    "    return errors\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a329dbd2-26d3-468e-89ed-ff2269bfa02d",
   "metadata": {},
   "outputs": [],
   "source": [
    "test_missing_glitch_single_offset_notarget(1633, 1000, 10, 20)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a445850e-19d5-4d64-94b0-c25682378b19",
   "metadata": {},
   "outputs": [],
   "source": [
    "test_missing_glitch_sweep_offset_notarget(clock=50e6, vco=600e6, span=scope.glitch.phase_shift_steps, width=500, num_glitches=1, reps=20, stepsize=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "516dd524-400c-4d54-a31d-2cfe7602cb7f",
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.trace.clock._warning_frequency = 401e6"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8ba704e0-4483-4701-8e43-73dd8dd2cfcf",
   "metadata": {},
   "outputs": [],
   "source": [
    "test_missing_glitch_sweep_offset_notarget(clock=100e6, vco=600e6, span=scope.glitch.phase_shift_steps, width=500, num_glitches=1, reps=20, stepsize=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "694bdbe7-c1ae-48c5-8f7c-6e66f1206067",
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.glitch.phase_shift_steps"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c0c15ddb-3272-4eea-a75b-118eaf51429f",
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.clock.pll.update_fpga_vco(1200e6)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "366cfa22-1d39-41ce-b193-027c5a3655d5",
   "metadata": {},
   "outputs": [],
   "source": [
    "test_missing_glitch_sweep_offset_notarget(clock=100e6, vco=1200e6, span=scope.glitch.phase_shift_steps, width=500, oversamp=4, num_glitches=1, reps=20, stepsize=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "28098f44-56da-421b-9458-851730ed95c4",
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.clock.adc_freq"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8022bcc5-db1c-4a0d-8edb-6da2e9299c52",
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.fpga_buildtime"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2de581d9-0eb3-403a-b267-656e5a1c7512",
   "metadata": {},
   "outputs": [],
   "source": [
    "errors"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "491809f2-7000-4cb5-8c38-d4a3828f4797",
   "metadata": {},
   "outputs": [],
   "source": [
    "glitchout    = scope.LA.extract(raw, 0)\n",
    "source       = scope.LA.extract(raw, 1)\n",
    "mmcm1out     = scope.LA.extract(raw, 2)\n",
    "mmcm2out     = scope.LA.extract(raw, 3)\n",
    "glitchgo     = scope.LA.extract(raw, 4)\n",
    "glitchenable = scope.LA.extract(raw, 6)\n",
    "glitchsource = scope.LA.extract(raw, 7)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a9c1301c-66b2-4704-81bb-630c1394bcaf",
   "metadata": {},
   "outputs": [],
   "source": [
    "from bokeh.plotting import figure, show\n",
    "from bokeh.resources import INLINE\n",
    "from bokeh.io import output_notebook\n",
    "from bokeh.models import Span, Legend, LegendItem\n",
    "import numpy as np\n",
    "output_notebook(INLINE)\n",
    "\n",
    "o = figure(width=1800)\n",
    "\n",
    "xrange = list(range(len(source)))\n",
    "O1 = o.line(xrange, source + 6, line_color='black')\n",
    "O2 = o.line(xrange, mmcm1out + 4, line_color='blue')\n",
    "O3 = o.line(xrange, mmcm2out + 2, line_color='red')\n",
    "O4 = o.line(xrange, glitchout + 0, line_color='purple', line_width=2)\n",
    "O5 = o.line(xrange, glitchenable - 2, line_color='black', line_width=2)\n",
    "O6 = o.line(xrange, glitchgo - 4, line_color='green')\n",
    "O7 = o.line(xrange, glitchsource - 6, line_color='pink', line_width=2)\n",
    "\n",
    "legend = Legend(items=[\n",
    "    LegendItem(label='source clock', renderers=[O1]),\n",
    "    LegendItem(label='glitch MMCM1 output (internal signal)', renderers=[O2]),\n",
    "    LegendItem(label='glitch MMCM2 output (internal signal)', renderers=[O3]),\n",
    "    LegendItem(label='glitch clock output', renderers=[O4]),\n",
    "    LegendItem(label='glitch enable', renderers=[O5]),\n",
    "    LegendItem(label='glitch go', renderers=[O6]),\n",
    "    LegendItem(label='glitch trigger source', renderers=[O7]),\n",
    "])\n",
    "o.add_layout(legend)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7ba9ee77-501b-4b5c-9007-d8ccd8ed9bf4",
   "metadata": {},
   "outputs": [],
   "source": [
    "show(o)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1fa0a51c-52fb-4771-a2ba-58557fc96757",
   "metadata": {},
   "outputs": [],
   "source": [
    "len(np.where(glitchenable == 1)[0])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "609f196a-5357-4a7e-8857-5a1b65da37d1",
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.glitch.state"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "446268ad-7bdd-4d4f-88be-3c77498cd8cf",
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.glitch.manual_trigger()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4ff8aab9-93c7-4988-bfb4-9f085f5cc5b3",
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.glitch.state"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d6082dfb-af87-4ea7-a592-de3e08c118c3",
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.glitch.state = None"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bfd43cc6-64f0-4a40-a77e-06eb1160ed4c",
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.glitch"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b587b8d6-4117-431b-9d24-4dbd5a59d804",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
